package com.netty.demo.server.loader;

import com.netty.demo.server.constant.ConstantValue;
import org.apache.ibatis.javassist.CannotCompileException;
import org.apache.ibatis.javassist.ClassPool;
import org.apache.ibatis.javassist.CtClass;
import org.apache.ibatis.javassist.CtMethod;
import org.apache.ibatis.javassist.CtNewMethod;
import org.apache.ibatis.javassist.NotFoundException;

import java.io.IOException;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.security.AccessController;
import java.security.PrivilegedAction;

/**
 * 动态类的加载器
 *
 * @author Administrator 余秀良
 * @version 1.0
 * <p>date: 2019年09月19日  11:44 </p>
 * <p>address: 四川成都 </p>
 */
public class DynamicClassLoader extends ClassLoader {
    private static DynamicClassLoader instance;

    public synchronized static DynamicClassLoader getInstance() {
        AccessController.doPrivileged((PrivilegedAction<Object>) () -> {
            if (instance == null) {
                instance = new DynamicClassLoader();
            }
            return null;
        });
        return instance;
    }

    public <T> Object get(String method, T clazz) {
        try {
            String className = clazz.getClass().getName();
            ClassPool classPool = ClassPool.getDefault();
            CtClass ctClass = classPool.get(className);
            CtMethod make = CtNewMethod.make(method, ctClass);
            ctClass.addMethod(make);
            byte[] bytes = ctClass.toBytecode();
            Class<?> aClass = defineClass(className, bytes, 0, bytes.length);
            Object newInstance = aClass.getDeclaredConstructor().newInstance();
            Field[] declaredFields = clazz.getClass().getDeclaredFields();
            for (Field oldInstanceField : declaredFields) {
                String name = oldInstanceField.getName();
                if (!name.equals(ConstantValue.SERIAL_VERSION_UID)) {
                    oldInstanceField.setAccessible(true);
                    Field newInstanceField = newInstance.getClass().getDeclaredField(name);
                    newInstanceField.setAccessible(true);
                    newInstanceField.set(newInstance, oldInstanceField.get(clazz));
                }
            }
            return newInstance;
        } catch (NotFoundException | CannotCompileException | IOException | NoSuchMethodException | InstantiationException | IllegalAccessException | InvocationTargetException | NoSuchFieldException e) {
            e.printStackTrace();
            return null;
        }
    }
}
